#include <iostream>

// Irrlicht and Cg Headers
#include "irrlicht.h"
#include <Cg/cg.h>

// IrrCg
#include "IrrCg.h"

// Irrlicht namespaces
using namespace irr;

using namespace core;
using namespace scene;
using namespace video;
using namespace io;
using namespace gui;

// Chernobyl Game Engine namespaces
using namespace IrrCg;

// Other namespaces
using namespace std;

IrrlichtDevice* Device = 0;
IVideoDriver* Driver = 0;
ISceneManager* SceneManager = 0;
stringw window = "";

class NormalMapCallBack : public ICgShaderConstantSetCallBack
{
public:
    CGparameter	WorldViewProjection;
    CGparameter	EyePosition;

    CGparameter	LightDiffuse;
    CGparameter	DecalMap;
    CGparameter	NormalMap;
    CGparameter	SpecularMap;

	virtual void OnSetConstants(ICgServices* services,CGprogram Vertex,CGprogram Pixel,const SMaterial &Material)
	{
        // Vertex Shader
        EyePosition = cgGetNamedParameter(Vertex, "eyePosition");
        vector3df CameraPosition = Device->getSceneManager()->getActiveCamera()->getAbsolutePosition();
        services->setParameter3f(EyePosition, CameraPosition.X, CameraPosition.Y, CameraPosition.Z);

        WorldViewProjection = cgGetNamedParameter(Vertex, "WorldViewProj");
        core::matrix4 worldViewProj;
		worldViewProj = Driver->getTransform(video::ETS_PROJECTION);
		worldViewProj *= Driver->getTransform(video::ETS_VIEW);
		worldViewProj *= Driver->getTransform(video::ETS_WORLD);
		services->setMatrix(WorldViewProjection,ICGT_MATRIX_IDENTITY,worldViewProj);

        // Pixel Shader
        DecalMap = cgGetNamedParameter(Pixel, "DecalMap");
        services->EnableTexture( DecalMap,Material.getTexture(0));

        NormalMap = cgGetNamedParameter(Pixel, "NormalMap");
        services->EnableTexture( NormalMap,Material.getTexture(1));

        SpecularMap = cgGetNamedParameter(Pixel, "SpecularMap");
        services->EnableTexture( SpecularMap,Material.getTexture(2) );

        LightDiffuse = cgGetNamedParameter(Pixel, "LightDiffuse");
        services->setParameter3f(LightDiffuse, 1.0f, 1.0f, 1.0f);
	}
};

int main()
{
    printf("\n *** IrrCg Normal Mapping Example by Patryk Nadrowski ***\n\n");

    char i;

    // Select Irrlicht Driver
    video::E_DRIVER_TYPE DriverType;
	printf("\n Please select the driver you want for this demo:\n\n"\
		"  (a) Direct3D 9.0c\n  (b) OpenGL 1.5\n"\
		"  (otherKey) exit\n\n");

	std::cin >> i;

	switch(i)
	{
		case 'a':
            DriverType = EDT_DIRECT3D9;
            window = " Direct3D9 PS: [ps_2_0] ";
            break;

		case 'b':
            DriverType = EDT_OPENGL;
            window = " OpenGL PS: [arbfp1] ";
            break;

		default: return 1;
	}

	Device = createDevice(DriverType,dimension2d<u32>(800 , 600),32,false,false,false,0);
	Driver = Device->getVideoDriver();
	SceneManager = Device->getSceneManager();

    // Create Cg Programming Service
    ICgProgrammingServices* GPU = new ICgProgrammingServices(Device);

    // Create Cg Shader Material
    NormalMapCallBack* NMCB = new NormalMapCallBack();
    s32 CgNormalMapMaterialType = GPU->addCgShaderMaterialFromFiles(CG_SOURCE,"../../cg/normalmapping.cg","main_v","arbvp1","vs_2_0","../../cg/normalmapping.cg","main_f","arbfp1","ps_2_0",NMCB,EMT_SOLID);

    // Create Node
    IAnimatedMesh* mesh = SceneManager->getMesh("../../media/DOOM3/imp.ms3d");

    ((ISkinnedMesh*)mesh)->convertMeshToTangents();

	IAnimatedMeshSceneNode* node = SceneManager->addAnimatedMeshSceneNode(mesh);
	node->setPosition(core::vector3df(0,0,0));
	node->setRotation(core::vector3df(0,180,0));
	node->setScale(core::vector3df(1,1,1));
	node->setAutomaticCulling(EAC_FRUSTUM_BOX);
	node->setMaterialFlag(video::EMF_LIGHTING, false);
	// Apply Cg Shader Material to Node
	node->setMaterialType((video::E_MATERIAL_TYPE)CgNormalMapMaterialType);

    node->getMaterial(0).TextureLayer[0].Texture = Driver->getTexture("../../media/DOOM3/imp_d.tga");
    node->getMaterial(0).TextureLayer[1].Texture = Driver->getTexture("../../media/DOOM3/imp_local.tga");
    node->getMaterial(0).TextureLayer[2].Texture = Driver->getTexture("../../media/DOOM3/imp_s.tga");

    // Create Camera
	scene::ICameraSceneNode* cam = SceneManager->addCameraSceneNodeFPS(0,100.0f,0.1f);
	cam->setPosition(core::vector3df(120,50,0));
	cam->setTarget(core::vector3df(0,0,0));

    // Hide Mouse Cursor
    Device->getCursorControl()->setVisible(false);

    // Last FPS
    int lastFPS = -1;

	while(Device->run())
    if (Device->isWindowActive())
	{
	    // Begin Scene
		Driver->beginScene(true, true, video::SColor(255,0,0,0));

        // Draw All
        SceneManager->drawAll();

        // End Scene
		Driver->endScene();

		int fps = Driver->getFPS();
		int poly = Driver->getPrimitiveCountDrawn();

		if (lastFPS != fps)
		{
		    core::stringw str = "";
            str = L"Irrlicht Cg - NormalMapping with Specular for";
            str += window;
            str += L" FPS: ";
            str += fps;
            str += L" Poly: ";
            str += poly;

            Device->setWindowCaption(str.c_str());
            lastFPS = fps;
		}
	}
	delete GPU;
	Device->drop();
	return 0;
}

